/*
 * Copyright (C) 2012 by Hao Hou<ghost89413@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>*
 */

#include <loader.h>
#include <dlfcn.h>
#include <init.h>
#include <xim.h>
#include <X11/Xutil.h>
#include <ic.h>
#include <UI.h>
#include <userconf.h>
void* __so_handler__[MAX_IME];
int   im_count = 0;
int   so_count = 0;
IMMetaData* immeta[MAX_IME];
int __default_imid__;
int __ime_commit_text__(char* text,void* IC)
{
	if(NULL == text || NULL == IC) return -1;
	IMIC* imic = (IMIC*)((char*)IC - sizeof(IMIC));
	CommitText(imic->IC,text);
	imic->IC->PreeditBuffer[0] = 0;
	imic->IC->PreeditBufferLength = 0;
	return 0;
}
int __ime_update_candidates__(char** list, int count , void* IC)
{
	if(NULL == list || NULL == IC) return -1;
	if(count < 0) return 0;
	
	IMIC* imic = (IMIC*)((char*)IC - sizeof(IMIC));
	return UpdateCandidates(imic->IC, list, count);	
}
char** __ime_get_candidates__(size_t* n, void* IC)
{
	if(NULL == n || NULL == IC) return NULL;
	IMIC* imic = (IMIC*)((char*)IC - sizeof(IMIC));
	(*n) = imic->IC->Candidates.Count;
	return imic->IC->Candidates.Candidate;
}
int __ime_update_precommit__(char* text, void* IC)
{

	if(NULL == text || NULL == IC) return -1;
	IMIC* imic = (IMIC*)((char*)IC - sizeof(IMIC));
	return UpdateCommit(imic->IC,text);
}
char* __ime_get_precommit__(void* IC)
{
	if(NULL == IC) return NULL;
	IMIC* imic = (IMIC*)((char*)IC - sizeof(IMIC));
	return imic->IC->PrecommitBuffer;
}

int __ime_reg_eventfilter__(uint32_t sym_left, uint32_t sym_right,  //[sym_left,sym_right)
			    uint32_t mask_neg, uint32_t mask_pos, uint32_t release,
			    int (*proc)(uint32_t, uint32_t, uint32_t ,void*),void* IC)
{
	if(NULL == IC) return -1;
	IMIC* imic = (IMIC*)((char*)IC - sizeof(IMIC));
	return SetForwardFilter(imic->IC, sym_left, sym_right,
			        mask_pos, mask_neg, release, proc);
}
int __ime_sw_eventfilter__(uint32_t fid, uint32_t enable, void* IC)
{
	if(NULL == IC) return -1;
	IMIC* imic = (IMIC*)((char*)IC - sizeof(IMIC));
	return SwitchForwardFilter(imic->IC, fid, enable);
}
int __ime_rst_eventfilter__(void* IC)
{
	if(NULL == IC) return 0;
	IMIC* imic = (IMIC*)((char*)IC - sizeof(IMIC));
	return ResetForwardFilter(imic->IC);
}
int __ime_set_trayicon__(const char* icon,void* IC)
{
	if(NULL == IC) return 0;
	IMIC* imic = (IMIC*)((char*)IC - sizeof(IMIC));
	strncpy(imic->IC->TrayIcon, icon, 256);
	return RedrawIC(imic->IC);	
}
int __ime_readconf__(const char* name,uint32_t type, void* buf,void* default_value,void* IC)
{
	char namebuf[256];
	if(NULL == IC) return 0;
	IMIC* imic = (IMIC*)((char*)IC - sizeof(IMIC));
	if(IC) snprintf(namebuf,256,"%s.%s",imic->IC->CurrentIM->Name, name);
	else   strncpy(namebuf,name,256);
	if(NULL == FetchValue(config, type, buf, default_value, namebuf)) return -1;
	return 0;
}
int LoadIME(const char* filename,char* param)
{
	if(so_count >= MAX_IME)
	{//Because im_count <= so_count , so it's fine does not check im_count
		VINE_LOG_ERROR("Too much IMEs");
		return -1;
	}
	__so_handler__[so_count] = dlopen(filename,RTLD_LAZY);
	if(NULL == __so_handler__[so_count])
	{
		VINE_LOG_ERROR("Can't open shared object %s %s",filename,strerror(errno));
		VINE_LOG_DEBUG("DLERROR = %s",dlerror());
		return -1;		
	}
	GetMetaDataProc get_meta = (GetMetaDataProc)dlsym(__so_handler__[so_count],GETMETADATASTR);
	if(NULL == get_meta)
	{
		VINE_LOG_ERROR("Can't find symbole %s needed in %s",GETMETADATASTR,filename);
		return -1;
	}
	so_count ++;
	IMMetaData* meta = immeta[im_count] = get_meta();
	if(NULL == meta)
	{
		VINE_LOG_ERROR("Can't get metadata from IM %s", filename);
		return -1;
	}
	meta->framework.Commit = __ime_commit_text__;
	meta->framework.UpdateCandidates = __ime_update_candidates__;
	meta->framework.GetCandidatesList = __ime_get_candidates__;
	meta->framework.UpdatePrecommitText = __ime_update_precommit__;
	meta->framework.GetPrecommitText = __ime_get_precommit__;
	meta->framework.RegisterForwardFilter = __ime_reg_eventfilter__;
	meta->framework.SwitchForwardFilter  = __ime_sw_eventfilter__;
	meta->framework.ResetForwardFilter = __ime_rst_eventfilter__;
	meta->framework.SetTrayIcon = __ime_set_trayicon__;
	meta->framework.ReadConfigure = __ime_readconf__;
	strncpy(meta->Parameter, param, sizeof(meta->Parameter));
	MAKE_FAKE_IC(fake_imic,meta);
	int ret = IME_CALL(meta,OnInitialize,meta->Parameter,fake_imic);
	if(ret == 0)
	{
		im_count ++;
		VINE_LOG_NOTICE("Input Method %s has been loaded successfully",meta->Name);
		return 0;	
	}
	return -1;
}
int InitIMEs()
{
	int def_count = 0;
	int *count,*def_im;
	int i;
	FetchValue(config,Integer,&count,&def_count,"IM.Count");
	FetchValue(config,Integer,&def_im,&def_count,"DefaultIM");
	for(i = 0 ; i < *count; i ++)
	{
		char* so_name;
		char* param;
		FetchValueEx(config,String,&so_name,NULL,"IM[%d].Binary",i);
		FetchValueEx(config,String,&param,"","IM[%d].StartupParameter",i);
		if(NULL == so_name) continue;
		if(*def_im == i) __default_imid__ = im_count; 
		if(so_name[0] && LoadIME(so_name,param) != 0)
		{
			VINE_LOG_WARNING("Input Method Load failed");
			if(i == *def_im) __default_imid__ = -1;
		}
		else
		{
			immeta[so_count-1]->id = i;
		}
	}
	return 0;
}
void FinalizeIMEs()
{
	int i;
	for(i = 0; i < im_count; i ++)
	{
		IME_CALL(immeta[i],OnFinalize);
		free(immeta[i]);   //That means that the IMMetaData should be get from malloc.
	}
	for(i = 0; i < so_count; i ++) dlclose(__so_handler__[i]);
}
IMMetaData*  GetDefaultIM()
{
	if(0 == im_count) return NULL;
	if(__default_imid__ == -1) return immeta[0];
	return immeta[__default_imid__];
}
IMMetaData* NextIM(IMMetaData* imdata)
{
	if(imdata == NULL) return GetDefaultIM();
	if(im_count == 0) return NULL;
	int id = imdata->id + 1;
	id %= im_count;
	return immeta[id];
}
